package com.facebook.swift.codec.metadata;

import static com.google.common.base.Preconditions.checkNotNull;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;

import com.facebook.swift.codec.metadata.ThriftExtraction;
import com.facebook.swift.codec.metadata.ThriftFieldExtractor;
import com.facebook.swift.codec.metadata.ThriftFieldMetadata;
import com.facebook.swift.codec.metadata.ThriftInjection;
import com.facebook.swift.codec.metadata.ThriftMethodExtractor;
import com.facebook.swift.codec.metadata.ThriftParameterInjection;
import com.google.common.base.Optional;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;

public class ThriftFieldMetadataUtil {

	private ThriftFieldMetadataUtil() {
	}

	private static ThriftInjection getFiledInjection(ThriftFieldMetadata input){
		List<ThriftInjection> injections = input.getInjections();		
		for(ThriftInjection e:injections){
			if(e.getId() == input.getId()){
				return e;
			}
		}
		throw new IllegalArgumentException("NOT FOUND ThriftInjection instance");
	}
	
	private static Field searchField(Class<?> clazz,String fieldName){
		if(clazz == null || fieldName == null){
			return null;
		} 
		for(Field field:clazz.getDeclaredFields()){
			if(!Modifier.isStatic(field.getModifiers()) && field.getName().equals(fieldName)){
				return field;
			}
		}
		return searchField(clazz.getSuperclass(),fieldName);
	}
	private static Method searchMethod(Class<?> clazz,String fieldName){
		if(clazz == null || fieldName == null){
			return null;
		}	
		String n = fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
		try {			
			return clazz.getMethod("get" +n);
		} catch (NoSuchMethodException e) {
			try {
				return clazz.getMethod("is" +n);
			} catch (NoSuchMethodException e1) {
				
			}
		}
		return null;
	}
	@SuppressWarnings("unchecked")
	public static <T extends Annotation> T extractFieldAnnotation(DecoratorThriftFieldMetadata input,Class<T> annClass) {
		if(input == null || annClass == null){
			return null;
		}

		ThriftInjection injection = getFiledInjection( input);
		Annotation[] fieldAnnotation;
		if(input.getExtraction().isPresent()){
			ThriftExtraction extraction = input.getExtraction().get();
			if(extraction instanceof ThriftMethodExtractor ){
				ThriftMethodExtractor thriftMethodExtractor = ((ThriftMethodExtractor)extraction);
				fieldAnnotation = thriftMethodExtractor.getMethod().getAnnotations();
				Optional<Annotation> found = Iterables.tryFind(Lists.newArrayList(fieldAnnotation), Predicates.instanceOf(annClass));
				if(found.isPresent()){
					return (T) found.get();
				}else{
					Field field = searchField(thriftMethodExtractor.getMethod().getDeclaringClass(),extraction.getName());
					if(field != null){
						fieldAnnotation = field.getAnnotations();
					}
				}
			}else if(extraction instanceof ThriftFieldExtractor ){
				ThriftFieldExtractor thriftFieldExtractor = ((ThriftFieldExtractor)extraction);
				fieldAnnotation = thriftFieldExtractor.getField().getAnnotations();
				Optional<Annotation> found = Iterables.tryFind(Lists.newArrayList(fieldAnnotation), Predicates.instanceOf(annClass));
				if(found.isPresent()){
					return (T) found.get();
				}else{
					Method method = searchMethod(thriftFieldExtractor.getField().getDeclaringClass(),extraction.getName());
					if(method != null){
						fieldAnnotation = method.getAnnotations();
					}
				}
			}else {
				throw new IllegalArgumentException("UNSUPPORTED ThriftExtraction TYPE " + extraction.getClass().getName());
			}
		}else {
			Method ownMethod;
			try {
				ownMethod = input.getMethodInjection().get().getMethod();	
			} catch (IllegalStateException e) {
				ownMethod = checkNotNull(input.getOwnedMethod(),"NOT DEFINED ownerMethod for " + input);
			}				
			fieldAnnotation = ownMethod.getParameterAnnotations()[((ThriftParameterInjection) injection).getParameterIndex()];
		}
		return (T) Iterables.tryFind(Lists.newArrayList(fieldAnnotation), Predicates.instanceOf(annClass)).orNull();
	
	}

}
